Skip to content

node: Validate and sanitize RPC URLs#4822

Draft
johnsaigle wants to merge 6 commits into
wormhole-foundation:mainfrom
johnsaigle:url-safety
Draft

node: Validate and sanitize RPC URLs#4822
johnsaigle wants to merge 6 commits into
wormhole-foundation:mainfrom
johnsaigle:url-safety

Conversation

@johnsaigle

Copy link
Copy Markdown
Contributor
  • Checks watcher's URLs against a deny-list of public RPC URLs. Logs an error when a public RPC URL is found for a delegated guardian. This should prevent problems where a delegate is not correct pointed at a full node.
  • Sanitizes URLs in logs so that path parts are not included. (These can sometimes contain API keys or other sensitive strings).
  • Normalizes logged field names across watchers for the 'watcher started' log

@johnsaigle johnsaigle added enhancement New feature or request node labels May 20, 2026
@johnsaigle
johnsaigle marked this pull request as ready for review May 21, 2026 15:23
Comment thread node/pkg/processor/processor.go
Comment thread node/pkg/processor/processor.go Outdated
}

// SafeURLForLogging returns only the hostname for a URL-like string.
// It intentionally omits userinfo, path, query, and fragment because those may contain credentials.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why also strip the path? I don't think that would realistically contain any secrets plus would be helpful to know the actual path delimited endpoint in some scenarios?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we need it. e.g. for Alchemy, they give you a URL like this: wss://eth-mainnet.g.alchemy.com/v2/W_...<secret here>..., and that would be part of the path. I don't know why they do it like that, but we have to work around it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good point, thanks

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason that we would WANT to log the path or query? If it's important enough, I could see a world where we only block/strip known patterns or use a regex to strip out specific portions of the path?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of a reason right now. I think we can go ahead with this change for now and do more advanced parsing later if we need it. It seems like a bit of a headache to try to think of every possible provider and analyze their APIs to satisfy a regex or identify known-bad patterns.

Comment thread node/pkg/watchers/watchers.go
djb15
djb15 previously approved these changes Jun 17, 2026
@johnsaigle

johnsaigle commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

@djb15 can you please re-review? I had to rebase and I also squashed the commits. Otherwise everything should be the same.

djb15
djb15 previously approved these changes Jun 23, 2026

@djb15 djb15 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the rebase didn't change anything drastic

- Checks watcher's URLs against a deny-list of public RPC URLs. Logs an
  error when a public RPC URL is found for a delegated guardian. This
  should prevent problems where a delegate is not correct pointed at a
  full node.
- Sanitizes URLs in logs so that path parts are not included. (These can
  sometimes contain API keys or other sensitive strings).
- Normalizes logged field names across watchers for the 'watcher
  started' log
if err != nil || parsedURL.Host == "" {
// Schemeless host:port strings parse as scheme:opaque. Parse them again
// as network-path references so URL.Hostname can extract the host.
parsedURL, err = url.Parse("//" + urlStr)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep the scheme? I don't see the harm and it would make debugging easier.

}

for _, rpcURL := range rpcURLs {
hostname := common.SafeURLForLogging(rpcURL)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is being used for things other than logging. Maybe worth considering a different name?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean exactly? I think it's always used to format a string for a log or else format a string that goes into an error message that then gets logged.

// It logs an error once per chain per run if a public RPC endpoint is detected.
// This is a safety measure: delegating guardians that use public RPC endpoints
// risk producing incorrect VAAs, which can cause consensus failures.
func (p *Processor) checkPublicRpcEndpoints() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is still safe for the ReobserveWithEndpoint calls. It's worth a double check though.

I think that preventing guardians from using public RPCs is a good call.

if err := p.dgc.Set(chains); err != nil {
p.logger.Error("delegate guardian config update failed", zap.Error(err))
} else {
p.checkPublicRpcEndpoints()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this just log an error or just it exit loudly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it exists here then I think it triggers a processor restart and crash loop. I think for the moment it's better to log and from there we can contract Guardians to figure out what's up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request node

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants